/*
 * JGrass - Free Open Source Java GIS http://www.jgrass.org 
 * (C) HydroloGIS - www.hydrologis.com 
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any
 * later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Library General Public License
 * along with this library; if not, write to the Free Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.jgrasstools.gears.io.dxfdwg.libs.dwg.utils;
import java.math.BigInteger;
import java.util.Arrays;

/**
 * Clase que engloba mtodos para trabajar con bytes. 
 *
 * @author Vicente Caballero Navarro
 */
public class ByteUtils {
    public static final int SIZE_BOOL = 1;
    public static final int SIZE_SHORT = 2;
    public static final int SIZE_INT = 4;
    public static final int SIZE_LONG = 8;
    public static final int SIZE_DOUBLE = 8;

    /** A nibble->char mapping for printing out bytes. */
    public static final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * Return the <code>int</code> represented by the bytes in
     * <code>data</code> staring at offset <code>offset[0]</code>.
     *
     * @param data the array from which to read
     * @param offset A single element array whose first element is the index in
     * 		  data from which to begin reading on function entry, and which on
     * 		  function exit has been incremented by the number of bytes read.
     *
     * @return the value of the <code>int</code> decoded
     */
    public static final int bytesToInt( byte[] data, int[] offset ) {
        /**
         * TODO: We use network-order within OceanStore, but temporarily
         * supporting intel-order to work with some JNI code until JNI code is
         * set to interoperate with network-order.
         */
        int result = 0;

        for( int i = 0; i < SIZE_INT; ++i ) {
            result <<= 8;
            result |= byteToUnsignedInt(data[offset[0]++]);
        }

        return result;
    }

    /**
     * Write the bytes representing <code>i</code> into the byte array
     * <code>data</code>, starting at index <code>offset [0]</code>, and
     * increment <code>offset [0]</code> by the number of bytes written; if
     * <code>data == null</code>, increment <code>offset [0]</code> by the
     * number of bytes that would have been written otherwise.
     *
     * @param i the <code>int</code> to encode
     * @param data The byte array to store into, or <code>null</code>.
     * @param offset A single element array whose first element is the index in
     * 		  data to begin writing at on function entry, and which on
     * 		  function exit has been incremented by the number of bytes
     * 		  written.
     */
    public static final void intToBytes( int i, byte[] data, int[] offset ) {
        /**
         * TODO: We use network-order within OceanStore, but temporarily
         * supporting intel-order to work with some JNI code until JNI code is
         * set to interoperate with network-order.
         */
        if (data != null) {
            for( int j = (offset[0] + SIZE_INT) - 1; j >= offset[0]; --j ) {
                data[j] = (byte) i;
                i >>= 8;
            }
        }

        offset[0] += SIZE_INT;
    }

    /**
     * Return the <code>short</code> represented by the bytes in
     * <code>data</code> staring at offset <code>offset[0]</code>.
     *
     * @param data the array from which to read
     * @param offset A single element array whose first element is the index in
     * 		  data from which to begin reading on function entry, and which on
     * 		  function exit has been incremented by the number of bytes read.
     *
     * @return the value of the <code>short</code> decoded
     */
    public static final short bytesToShort( byte[] data, int[] offset ) {
        /**
         * TODO: We use network-order within OceanStore, but temporarily
         * supporting intel-order to work with some JNI code until JNI code is
         * set to interoperate with network-order.
         */
        short result = 0;

        for( int i = 0; i < SIZE_SHORT; ++i ) {
            result <<= 8;
            result |= (short) byteToUnsignedInt(data[offset[0]++]);
        }

        return result;
    }

    /**
     * Write the bytes representing <code>s</code> into the byte array
     * <code>data</code>, starting at index <code>offset [0]</code>, and
     * increment <code>offset [0]</code> by the number of bytes written; if
     * <code>data == null</code>, increment <code>offset [0]</code> by the
     * number of bytes that would have been written otherwise.
     *
     * @param s the <code>short</code> to encode
     * @param data The byte array to store into, or <code>null</code>.
     * @param offset A single element array whose first element is the index in
     * 		  data to begin writing at on function entry, and which on
     * 		  function exit has been incremented by the number of bytes
     * 		  written.
     */
    public static final void shortToBytes( short s, byte[] data, int[] offset ) {
        /**
         * TODO: We use network-order within OceanStore, but temporarily
         * supporting intel-order to work with some JNI code until JNI code is
         * set to interoperate with network-order.
         */
        if (data != null) {
            data[offset[0] + 1] = (byte) s;
            data[offset[0]] = (byte) (s >> 8);
        }

        offset[0] += SIZE_SHORT;
    }

    /**
     * Return the <code>long</code> represented by the bytes in
     * <code>data</code> staring at offset <code>offset[0]</code>.
     *
     * @param data the array from which to read
     * @param offset A single element array whose first element is the index in
     * 		  data from which to begin reading on  function entry, and which
     * 		  on function exit has been incremented by the number of bytes
     * 		  read.
     *
     * @return the value of the <code>long</code> decoded
     */
    public static final long bytesToLong( byte[] data, int[] offset ) {
        long result = 0;

        for( int i = 0; i < SIZE_LONG; ++i ) {
            result <<= 8;

            int res = byteToUnsignedInt(data[offset[0]++]);
            result = result | res;
        }

        return result;
    }

    /**
     * Write the bytes representing <code>l</code> into the byte array
     * <code>data</code>, starting at index <code>offset [0]</code>, and
     * increment <code>offset [0]</code> by the number of bytes written; if
     * <code>data == null</code>, increment <code>offset [0]</code> by the
     * number of bytes that would have been written otherwise.
     *
     * @param l the <code>long</code> to encode
     * @param data The byte array to store into, or <code>null</code>.
     * @param offset A single element array whose first element is the index in
     * 		  data to begin writing at on function entry, and which on
     * 		  function exit has been incremented by the number of bytes
     * 		  written.
     */
    public static final void longToBytes( long l, byte[] data, int[] offset ) {
        /**
         * TODO: We use network-order within OceanStore, but temporarily
         * supporting intel-order to work with some JNI code until JNI code is
         * set to interoperate with network-order.
         */
        if (data != null) {
            for( int j = (offset[0] + SIZE_LONG) - 1; j >= offset[0]; --j ) {
                data[j] = (byte) l;
                l >>= 8;
            }
        }

        offset[0] += SIZE_LONG;
    }

    /**
     * Return the <code>double</code> represented by the bytes in
     * <code>data</code> staring at offset <code>offset[0]</code>.
     *
     * @param data the array from which to read
     * @param offset A single element array whose first element is the index in
     * 		  data from which to begin reading on  function entry, and which
     * 		  on function exit has been incremented by the number of bytes
     * 		  read.
     *
     * @return the value of the <code>double</code> decoded
     */
    public static final double bytesToDouble( byte[] data, int[] offset ) {
        long bits = bytesToLong(data, offset);

        return Double.longBitsToDouble(bits);
    }

    /**
     * Write the bytes representing <code>d</code> into the byte array
     * <code>data</code>, starting at index <code>offset [0]</code>, and
     * increment <code>offset [0]</code> by the number of bytes written; if
     * <code>data == null</code>, increment <code>offset [0]</code> by the
     * number of bytes that would have been written otherwise.
     *
     * @param d the <code>double</code> to encode
     * @param data The byte array to store into, or <code>null</code>.
     * @param offset A single element array whose first element is the index in
     * 		  data to begin writing at on function entry, and which on
     * 		  function exit has been incremented by the number of bytes
     * 		  written.
     */
    public static final void doubleToBytes( double d, byte[] data, int[] offset ) {
        long bits = Double.doubleToLongBits(d);
        longToBytes(bits, data, offset);
    }

    /**
     * Return the <code>String</code> represented by the bytes in
     * <code>data</code> staring at offset <code>offset[0]</code>. This method
     * relies on the user using the corresponding <code>stringToBytes</code>
     * method to encode the <code>String</code>, so that it may properly
     * retrieve the <code>String</code> length.
     *
     * @param data the array from which to read
     * @param offset A single element array whose first element is the index in
     * 		  data from which to begin reading on function entry, and which on
     * 		  function exit has been incremented by the number of bytes read.
     *
     * @return the value of the <code>String</code> decoded
     */
    public static final String bytesToString( byte[] data, int[] offset ) {
        offset[0] = 0;

        int length = bytesToInt(data, offset);
        String st = null;

        if ((length < 0) || (length > data.length)) {
            st = new String(data);
        } else {
            st = new String(data, offset[0], length);
        }

        offset[0] += length;

        return st;
    }

    /**
     * Write the bytes representing <code>s</code> into the byte array
     * <code>data</code>, starting at index <code>offset [0]</code>, and
     * increment <code>offset [0]</code> by the number of bytes written; if
     * <code>data == null</code>, increment <code>offset [0]</code> by the
     * number of bytes that would have been written otherwise.
     *
     * @param s the <code>String</code> to encode
     * @param data The byte array to store into, or <code>null</code>.
     * @param offset A single element array whose first element is the index in
     * 		  data to begin writing at on function entry, and which on
     * 		  function exit has been incremented by the number of bytes
     * 		  written.
     */
    public static final void stringToBytes( String s, byte[] data, int[] offset ) {
        byte[] s_bytes = s.getBytes();

        if (data != null) {
            intToBytes(s_bytes.length, data, offset);
            memcpy(data, offset[0], s_bytes, 0, s_bytes.length);
        } else {
            offset[0] += SIZE_INT;
        }

        offset[0] += s_bytes.length;
    }

    /**
     * Return the <code>boolean</code> represented by the bytes in
     * <code>data</code> staring at offset <code>offset[0]</code>.
     *
     * @param data the array from which to read
     * @param offset A single element array whose first element is the index in
     * 		  data from which to begin reading on  function entry, and which
     * 		  on function exit has been incremented by the number of bytes
     * 		  read.
     *
     * @return the value of the <code>boolean</code> decoded
     */
    public static final boolean bytesToBool( byte[] data, int[] offset ) {
        boolean result = true;

        if (data[offset[0]] == 0) {
            result = false;
        }

        offset[0] += SIZE_BOOL;

        return result;
    }

    /**
     * Write the bytes representing <code>b</code> into the byte array
     * <code>data</code>, starting at index <code>offset [0]</code>, and
     * increment <code>offset [0]</code> by the number of bytes written; if
     * <code>data == null</code>, increment <code>offset [0]</code> by the
     * number of bytes that would have been written otherwise.
     *
     * @param b the <code>boolean</code> to encode
     * @param data The byte array to store into, or <code>null</code>.
     * @param offset A single element array whose first element is the index in
     * 		  data to begin writing at on function entry, and which on
     * 		  function exit has been incremented by the number of bytes
     * 		  written.
     */
    public static final void boolToBytes( boolean b, byte[] data, int[] offset ) {
        if (data != null) {
            data[offset[0]] = (byte) (b ? 1 : 0);
        }

        offset[0] += SIZE_BOOL;
    }

    /**
     * Return the <code>BigInteger</code> represented by the bytes in
     * <code>data</code> staring at offset <code>offset[0]</code>.
     *
     * @param data the array from which to read
     * @param offset A single element array whose first element is the index in
     * 		  data from which to begin reading on  function entry, and which
     * 		  on function exit has been incremented by the number of bytes
     * 		  read.
     *
     * @return the <code>BigInteger</code> decoded
     */
    public static final BigInteger bytesToBigInteger( byte[] data, int[] offset ) {
        int length = bytesToInt(data, offset);
        byte[] bytes = new byte[length];
        offset[0] += memcpy(bytes, 0, data, offset[0], length);

        return new BigInteger(bytes);
    }

    /**
     * Write the bytes representing <code>n</code> into the byte array
     * <code>data</code>, starting at index <code>offset [0]</code>, and
     * increment <code>offset [0]</code> by the number of bytes written; if
     * <code>data == null</code>, increment <code>offset [0]</code> by the
     * number of bytes that would have been written otherwise.
     *
     * @param n the <code>BigInteger</code> to encode
     * @param data The byte array to store into, or <code>null</code>.
     * @param offset A single element array whose first element is the index in
     * 		  data to begin writing at on function entry, and which on
     * 		  function exit has been incremented by the number of bytes
     * 		  written.
     */
    public static final void bigIntegerToBytes( BigInteger n, byte[] data, int[] offset ) {
        byte[] bytes = n.toByteArray();
        intToBytes(bytes.length, data, offset);
        offset[0] += memcpy(data, offset[0], bytes, 0, bytes.length);
    }

    /**
     * Convert an array of <code>bytes</code>s into an array of
     * <code>ints</code>.
     *
     * @param dst the array to write
     * @param dst_offset the start offset in <code>dst</code>, times 4. This
     * 		  measures the offset as if <code>dst</code> were an array of
     * 		  <code>byte</code>s (rather than <code>int</code>s).
     * @param src the array to read
     * @param src_offset the start offset in <code>src</code>
     * @param length the number of <code>byte</code>s to copy.
     */
    public static final void bytesToInts( int[] dst, int dst_offset, byte[] src, int src_offset, int length ) {
        if ((src == null) || (dst == null) || ((src_offset + length) > src.length) || ((dst_offset + length) > (dst.length * 4))
                || ((dst_offset % 4) != 0) || ((length % 4) != 0)) {
            croak("bytesToInts parameters are invalid"
                    + " src=="
                    + Arrays.toString(src)
                    + " dst=="
                    + Arrays.toString(dst)
                    + (((src == null) || (dst == null)) ? " " : (" (src_offset+length)>src.length==" + (src_offset + length)
                            + ">" + src.length + " (dst_offset+length)>(dst.length*4)==" + (dst_offset + length) + ">"
                            + (dst.length * 4) + " (dst_offset%4)==" + (dst_offset % 4) + " (length%4)==" + (length % 4)
                            + " dest.length==" + dst.length + " length==" + length)));
        }

        // Convert parameters to normal format
        int[] offset = new int[1];
        offset[0] = src_offset;

        int int_dst_offset = dst_offset / 4;

        for( int i = 0; i < (length / 4); ++i ) {
            dst[int_dst_offset++] = bytesToInt(src, offset);
        }
    }

    /**
     * Convert an array of <code>int</code>s into an array of
     * <code>bytes</code>.
     *
     * @param dst the array to write
     * @param dst_offset the start offset in <code>dst</code>
     * @param src the array to read
     * @param src_offset the start offset in <code>src</code>, times 4. This
     * 		  measures the offset as if <code>src</code> were an array of
     * 		  <code>byte</code>s (rather than <code>int</code>s).
     * @param length the number of <code>byte</code>s to copy.
     */
    public static final void intsToBytes( byte[] dst, int dst_offset, int[] src, int src_offset, int length ) {
        if ((src == null) || (dst == null) || ((dst_offset + length) > dst.length) || ((src_offset + length) > (src.length * 4))
                || ((src_offset % 4) != 0) || ((length % 4) != 0)) {
            croak("intsToBytes parameters are invalid:" + " src=" + Arrays.toString(src) + " dst=" + Arrays.toString(dst)
                    + " (dst_offset=" + dst_offset + " + length=" + length + ")=" + (dst_offset + length) + " > dst.length="
                    + ((dst == null) ? 0 : dst.length) + " (src_offset=" + src_offset + " + length=" + length + ")="
                    + (src_offset + length) + " > (src.length=" + ((src == null) ? 0 : src.length) + "*4)="
                    + ((src == null) ? 0 : (src.length * 4)) + " (src_offset=" + src_offset + " % 4)=" + (src_offset % 4)
                    + " != 0" + " (length=" + length + " % 4)=" + (length % 4) + " != 0");
        }

        // Convert parameters to normal format
        int[] offset = new int[1];
        offset[0] = dst_offset;

        int int_src_offset = src_offset / 4;

        for( int i = 0; i < (length / 4); ++i ) {
            intToBytes(src[int_src_offset++], dst, offset);
        }
    }

    /**
     * Convert a <code>byte</code> into an unsigned integer.
     *
     * @param b the <code>byte</code> to cast
     *
     * @return a postiive <code>int</code> whose lowest byte contains the bits
     * 		   of <code>b</code>.
     */
    public static final int byteToUnsignedInt( byte b ) {
        return ((int) b) & 0xff;
    }

    /**
     * Copy contents of one array of <code>bytes</code> into another. If either
     * array is <code>null</code>, simply return the <code>length</code>
     * parameter directly.
     *
     * @param dst the array to write, or <code>null</code>
     * @param dst_offset the start offset in <code>dst</code>
     * @param src the array to read, or <code>null</code>
     * @param src_offset the start offset in <code>src</code>
     * @param length the number of <code>byte</code>s to copy.
     *
     * @return DOCUMENT ME!
     */
    public static int memcpy( byte[] dst, int dst_offset, byte[] src, int src_offset, int length ) {
        if ((dst != null) && (src != null)) {
            if (dst.length < (dst_offset + length)) {
                croak("dst.length = " + dst.length + ", but " + "dst_offset = " + dst_offset + " and length = " + length + ".");
            }

            if (src.length < (src_offset + length)) {
                croak("src.length = " + src.length + ", but " + "src_offset = " + src_offset + " and length = " + length + ".");
            }

            for( int i = 0; i < length; ++i, ++dst_offset, ++src_offset )
                dst[dst_offset] = src[src_offset];
        }

        return length;
    }

    /**
     * Compare the contents of one array of <code>bytes</code> to another.
     *
     * @param a the first array
     * @param a_offset the start offset in <code>a</code>
     * @param b the second array
     * @param b_offset the start offset in <code>b</code>
     * @param length the number of <code>byte</code>s to compare.
     *
     * @return DOCUMENT ME!
     */
    public static boolean memcmp( byte[] a, int a_offset, byte[] b, int b_offset, int length ) {
        if ((a == null) && (b == null)) {
            return true;
        }

        if ((a == null) || (b == null)) {
            return false;
        }

        for( int i = 0; i < length; ++i, ++a_offset, ++b_offset )
            if (a[a_offset] != b[b_offset]) {
                return false;
            }

        return true;
    }

    /**
     * Fill the given array with zeros.
     *
     * @param array the array to clear
     * @param offset the start offset
     * @param length the number of <code>byte</code>s to clear.
     */
    public static void memclr( byte[] array, int offset, int length ) {
        for( int i = 0; i < length; ++i, ++offset )
            array[offset] = 0;
    }

    /**
     * Round a number up to a given multiple.
     *
     * @param value the number to be rounded
     * @param multiple the number to which to be rounded
     *
     * @return the smallest <code>int</code> greater than or equal to
     * 		   <code>value</code> which divides <code>multiple</code> exactly.
     */
    public static int round_up( int value, int multiple ) {
        return (((value - 1) / multiple) + 1) * multiple;
    }

    /**
     * Return a new array equal to original except zero-padded to an integral
     * mulitple of blocks.  If the original is already an integral multiple of
     * blocks, just return it.
     *
     * @param original the array of <code>byte</code>s to be padded
     * @param block_size the size of the blocks
     *
     * @return an array whose size divides <code>block_size</code> exactly. The
     * 		   array is either <code>original</code> itself, or a copy whose
     * 		   first <code>original.length</code> bytes are equal to
     * 		   <code>original</code>.
     */
    public static byte[] zero_pad( byte[] original, int block_size ) {
        if ((original.length % block_size) == 0) {
            return original;
        }

        byte[] result = new byte[round_up(original.length, block_size)];
        memcpy(result, 0, original, 0, original.length);

        // Unnecessary - jvm sets bytes to 0.
        // memclr (result, original.length, result.length - original.length);
        return result;
    }

    /**
     * Determines whether two arrays of <code>byte</code>s contain the same
     * contents.
     *
     * @param b1 The first array
     * @param b2 The second array
     *
     * @return <code>true</code> if both arrays are <code>null</code>, both
     * 		   empty, or both of the same length with equal contents.
     */
    public static boolean equals( byte[] b1, byte[] b2 ) {
        if (b1 == b2) {
            return true;
        }

        if ((b1 == null) || (b2 == null)) { // only one is null

            return false;
        }

        if (b1.length != b2.length) {
            return false;
        }

        for( int i = 0; i < b1.length; ++i )
            if (b1[i] != b2[i]) {
                return false;
            }

        return true;
    }

    /**
     * Produce a <code>String</code> representation for the specified array of
     * <code>byte</code>s.  Print each <code>byte</code> as two hexadecimal
     * digits.
     *
     * @param data The array to print
     * @param offset the start offset in <code>data</code>
     * @param length the number of <code>byte</code>s to print
     *
     * @return DOCUMENT ME!
     */
    public static String print_bytes( byte[] data, int offset, int length ) {
        int size = 2 * length;
        size += ((size / 8) + (size / 64));

        char[] buf = new char[size];
        int low_mask = 0x0f;
        int high_mask = 0xf0;
        int buf_pos = 0;
        byte b;

        int j = 0;

        for( int i = offset; i < (offset + length); ++i ) {
            b = data[i];
            buf[buf_pos++] = digits[(high_mask & b) >> 4];
            buf[buf_pos++] = digits[(low_mask & b)];

            if ((j % 4) == 3) {
                buf[buf_pos++] = ' ';
            }

            if ((j % 32) == 31) {
                buf[buf_pos++] = '\n';
            }

            ++j;
        }

        return new String(buf);
    }

    /**
     * DOCUMENT ME!
     *
     * @param data DOCUMENT ME!
     * @param offset DOCUMENT ME!
     * @param length DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public static String print_bytes_exact( byte[] data, int offset, int length ) {
        int size = 2 * length;
        char[] buf = new char[size];
        int low_mask = 0x0f;
        int high_mask = 0xf0;
        int buf_pos = 0;
        byte b;

        int j = 0;

        for( int i = offset; i < (offset + length); ++i ) {
            b = data[i];
            buf[buf_pos++] = digits[(high_mask & b) >> 4];
            buf[buf_pos++] = digits[(low_mask & b)];
            ++j;
        }

        return new String(buf);
    }

    /**
     * DOCUMENT ME!
     *
     * @param data DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public static String print_bytes( byte[] data ) {
        return print_bytes(data, 0, data.length);
    }

    /**
     * DOCUMENT ME!
     *
     * @param msg DOCUMENT ME!
     */
    private static void croak( String msg ) {
        // throw new java.AssertionViolatedException(msg);
    }

    /**
     * DOCUMENT ME!
     *
     * @param b DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public static int getUnsigned( byte b ) {
        return ((b & 0xff)); // >> 8);
    }
}
